package com.fanwj.citrus

import android.graphics.Bitmap
import android.hardware.HardwareBuffer
import android.os.Handler
import android.os.HandlerThread
import android.os.MessageQueue.OnFileDescriptorEventListener
import android.os.ParcelFileDescriptor
import android.system.Os
import android.view.SurfaceHolder
import com.fanwj.citrus.aidl.IBackend
import com.fanwj.citrus.aidl.android.os.IBinder
import com.fanwj.citrus.aidl.android.os.Parcel
import com.fanwj.citrus.aidl.android.os.RemoteException
import java.io.FileDescriptor
import java.io.IOException
import java.lang.ref.WeakReference

object Backend : HandlerThread("Backend"), OnFileDescriptorEventListener, IBinder {

    lateinit var stub: IBackend private set

    private val threadGroup = ThreadGroup("Backend")

    private val frontend = Frontend()

    private val messageFd = ThreadLocal<ParcelFileDescriptor>()

    private lateinit var fileDescriptorFd: ParcelFileDescriptor

    private lateinit var hardwareBufferFd: ParcelFileDescriptor

    private lateinit var vibratorFd: ParcelFileDescriptor

    private var surface = WeakReference<SurfaceHolder>(null)

    private fun recvHardwareBuffer(): HardwareBuffer? {
        return nativeRecvHardwareBuffer(hardwareBufferFd.fd)
    }

    fun sendFileDescriptor(fd: ParcelFileDescriptor) {
        nativeSendFileDescriptor(fileDescriptorFd.fd, fd.fd)
    }

    fun setSurface(surface: SurfaceHolder?) {
        Handler(looper).post {
            this.surface = WeakReference(surface)
        }
    }

    override fun onFileDescriptorEvents(fd: FileDescriptor, events: Int): Int {
        try {
            when (fd) {
                fileDescriptorFd.fileDescriptor -> {
                    val nativeFd = nativeRecvFileDescriptor(fileDescriptorFd.fd)
                    if (nativeFd < 0) {
                        CitrusApplication.application.exit()
                        return -1
                    }
                    val newFd = ParcelFileDescriptor.fromFd(nativeFd)
                    Thread(threadGroup) {
                        this.messageFd.set(newFd)
                        val data = Parcel.obtain()
                        val reply = Parcel.obtain()
                        try {
                            request(data, reply, newFd.fd)
                        } catch (e: RemoteException) {
                            e.printStackTrace()
                        }
                        data.recycle()
                        reply.recycle()
                    }.start()
                }

                hardwareBufferFd.fileDescriptor -> {
                    recvHardwareBuffer()?.use {
                        this.surface.get()?.run {
                            if (surface.isValid) {
                                Bitmap.wrapHardwareBuffer(it, null)?.let { bitmap ->
                                    unlockCanvasAndPost(lockHardwareCanvas().apply {
                                        drawBitmap(bitmap, 0f, 0f, null)
                                    })
                                    bitmap.recycle()
                                }
                            }
                        }
                    }
                }

                vibratorFd.fileDescriptor -> {
                    val buffer = ByteArray(8)
                    Os.read(vibratorFd.fileDescriptor, buffer, 0, buffer.size)
                    val minTime = buffer[0].toUInt().or(buffer[1].toUInt().shl(8))
                    val maxTime = buffer[2].toUInt().or(buffer[3].toUInt().shl(8))
                    val minAmplitude = buffer[4].toUInt()
                    val maxAmplitude = buffer[6].toUInt()
                    CitrusApplication.application.vibrate(
                        ((minTime + maxTime).shr(1)).toLong(),
                        (minAmplitude + maxAmplitude).shr(1).toInt()
                    )
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            looper.queue.addOnFileDescriptorEventListener(fd, events, this)
        }
        return 0
    }

    override fun onLooperPrepared() {
        val events = OnFileDescriptorEventListener.EVENT_INPUT
        val queue = this.looper.queue
        queue.addOnFileDescriptorEventListener(fileDescriptorFd.fileDescriptor, events, this)
        queue.addOnFileDescriptorEventListener(hardwareBufferFd.fileDescriptor, events, this)
        queue.addOnFileDescriptorEventListener(vibratorFd.fileDescriptor, events, this)
    }

    @kotlin.Throws(RemoteException::class)
    override fun transact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
        return this.messageFd.get()?.fd?.let { fd ->
            nativeSendMessage(fd, code, data.get())
            return reply?.run {
                setDataSize(0)
                return -code == request(this, data, fd)
            } ?: true
        } ?: let {
            val ret = arrayOf(false)
            NativeLibrary.postToMainLooper {
                ret[0] = transact(code, data, reply, flags)
            }
            return ret[0]
        }
    }

    @kotlin.Throws(RemoteException::class)
    private fun request(data: Parcel, reply: Parcel, fd: Int): Int {
        while (true) {
            val code = nativeRecvMessage(fd, data.get())
            if (code < 0) return code
            else if (0 == code) throw RemoteException("Backend disconnected")
            reply.setDataSize(0)
            frontend.onTransact(code, data, reply, 0)
            nativeSendMessage(fd, -code, reply.get())
        }
    }

    @Synchronized
    fun getThreadById(id: Long): Thread? {
        val count = threadGroup.activeCount()
        if (count < 1) return null
        val threads: Array<Thread?> = Array(count) { null }
        threadGroup.enumerate(threads)
        threads.all {
            it?.apply {
                if (this.id == id) {
                    return@getThreadById this
                }
            }
            true
        }
        return null
    }

    fun initialize(fd: ParcelFileDescriptor): Backend {
        this.fileDescriptorFd = fd
        this.hardwareBufferFd = ParcelFileDescriptor.fromFd(nativeRecvFileDescriptor(fd.fd))
        this.vibratorFd = ParcelFileDescriptor.fromFd(nativeRecvFileDescriptor(fd.fd))
        this.stub = IBackend.Stub.asInterface(this)
        return this
    }

    fun handleException(e: Exception) {
        e.printStackTrace()
        NativeLibrary.stopMainLooper(-1)
        CitrusApplication.application.exit()
    }
}